//------------------------------------------------------------------------------
// <copyright file="OdbcHandle.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Runtime.ConstrainedExecution;

namespace System.Data.Odbc {

    internal abstract class OdbcHandle : SafeHandle {

        private ODBC32.SQL_HANDLE _handleType;
        private OdbcHandle        _parentHandle;

        protected OdbcHandle(ODBC32.SQL_HANDLE handleType, OdbcHandle parentHandle) : base(IntPtr.Zero, true) {

            _handleType   = handleType;

            bool mustRelease = false;
            ODBC32.RetCode retcode = ODBC32.RetCode.SUCCESS;

            // using ConstrainedRegions to make the native ODBC call and AddRef the parent
            RuntimeHelpers.PrepareConstrainedRegions();
            try {
                // validate handleType
                switch(handleType) {
                case ODBC32.SQL_HANDLE.ENV:
                    Debug.Assert(null == parentHandle, "did not expect a parent handle");
                    retcode = UnsafeNativeMethods.SQLAllocHandle(handleType, IntPtr.Zero, out base.handle);
                    break;
                case ODBC32.SQL_HANDLE.DBC:
                case ODBC32.SQL_HANDLE.STMT:
                    // must addref before calling native so it won't be released just after
                    Debug.Assert(null != parentHandle, "expected a parent handle"); // safehandle can't be null
                    parentHandle.DangerousAddRef(ref mustRelease);

                    retcode = UnsafeNativeMethods.SQLAllocHandle(handleType, parentHandle, out base.handle);
                    break;
//              case ODBC32.SQL_HANDLE.DESC:
                default:
                    Debug.Assert(false, "unexpected handleType");
                    break;
                }
            }
            finally {
                if (mustRelease) {
                    switch(handleType) {
                    case ODBC32.SQL_HANDLE.DBC:
                    case ODBC32.SQL_HANDLE.STMT:
                        if (IntPtr.Zero != base.handle) {
                            // must assign _parentHandle after a handle is actually created
                            // since ReleaseHandle will only call DangerousRelease if a handle exists
                            _parentHandle = parentHandle;
                        }
                        else {
                            // without a handle, ReleaseHandle may not be called
                            parentHandle.DangerousRelease();
                        }
                        break;
                    }
                }
            }
            Bid.TraceSqlReturn("<odbc.SQLAllocHandle|API|ODBC|RET> %08X{SQLRETURN}\n", retcode);

            if((ADP.PtrZero == base.handle) || (ODBC32.RetCode.SUCCESS != retcode)) {
                // 
                throw ODBC.CantAllocateEnvironmentHandle(retcode);
            }
        }
        
        internal OdbcHandle(OdbcStatementHandle parentHandle, ODBC32.SQL_ATTR attribute) : base(IntPtr.Zero, true) {
            Debug.Assert((ODBC32.SQL_ATTR.APP_PARAM_DESC == attribute) || (ODBC32.SQL_ATTR.APP_ROW_DESC == attribute), "invalid attribute");
            _handleType   = ODBC32.SQL_HANDLE.DESC;

            int cbActual;
            ODBC32.RetCode retcode;
            bool mustRelease = false;
            RuntimeHelpers.PrepareConstrainedRegions();
            try {
                // must addref before calling native so it won't be released just after
                parentHandle.DangerousAddRef(ref mustRelease);

                retcode = parentHandle.GetStatementAttribute(attribute, out base.handle, out cbActual);
            }
            finally {
                if (mustRelease) {
                    if (IntPtr.Zero != base.handle) {
                        // must call DangerousAddRef after a handle is actually created
                        // since ReleaseHandle will only call DangerousRelease if a handle exists
                        _parentHandle = parentHandle;
                    }
                    else {
                        // without a handle, ReleaseHandle may not be called
                        parentHandle.DangerousRelease();
                    }
                }
            }
            if (ADP.PtrZero == base.handle) {
                throw ODBC.FailedToGetDescriptorHandle(retcode);
            }
            // no info-message handle on getting a descriptor handle
        }

        internal ODBC32.SQL_HANDLE HandleType {
            get {
                return _handleType;
            }
        }

        public override bool IsInvalid {
            get {
                // we should not have a parent if we do not have a handle
                return (IntPtr.Zero == base.handle);
            }
        }

        override protected bool ReleaseHandle() {
            // NOTE: The SafeHandle class guarantees this will be called exactly once and is non-interrutible.
            IntPtr handle = base.handle;
            base.handle = IntPtr.Zero;

            if (IntPtr.Zero != handle) {
                ODBC32.SQL_HANDLE handleType   = HandleType;

                switch (handleType) {
                case ODBC32.SQL_HANDLE.DBC:
                    // Disconnect happens in OdbcConnectionHandle.ReleaseHandle
                case ODBC32.SQL_HANDLE.ENV:
                case ODBC32.SQL_HANDLE.STMT:
                    ODBC32.RetCode retcode = UnsafeNativeMethods.SQLFreeHandle(handleType, handle);
                    Bid.TraceSqlReturn("<odbc.SQLFreeHandle|API|ODBC|RET> %08X{SQLRETURN}\n", retcode);
                    break;

                case ODBC32.SQL_HANDLE.DESC:
                    // nothing to free on the handle
                    break;

                // case 0: ThreadAbortException setting handle before HandleType
                default:
                    Debug.Assert(ADP.PtrZero == handle, "unknown handle type");
                    break;
                }
            }

            // If we ended up getting released, then we have to release
            // our reference on our parent.
            OdbcHandle parentHandle = _parentHandle;
            _parentHandle = null;
            if (null != parentHandle) {
                parentHandle.DangerousRelease();
                parentHandle = null;
            }
            return true;
        }

        internal ODBC32.RetCode GetDiagnosticField (out string sqlState) {
            short cbActual;
            // ODBC (MSDN) documents it expects a buffer large enough to hold 5(+L'\0') unicode characters
            StringBuilder sb = new StringBuilder(6);
            ODBC32.RetCode retcode = UnsafeNativeMethods.SQLGetDiagFieldW(
                HandleType,
                this,
                (short)1,
                ODBC32.SQL_DIAG_SQLSTATE,
                sb,
                checked((short)(2*sb.Capacity)), // expects number of bytes, see \\kbinternal\kb\articles\294\1\69.HTM
                out cbActual);
            ODBC.TraceODBC(3, "SQLGetDiagFieldW", retcode);
            if ((retcode == ODBC32.RetCode.SUCCESS) || (retcode == ODBC32.RetCode.SUCCESS_WITH_INFO)) {
                sqlState = sb.ToString();
            }
            else {
                sqlState = ADP.StrEmpty;
            }
            return retcode;
        }

        internal ODBC32.RetCode GetDiagnosticRecord(short record, out string sqlState, StringBuilder message, out int nativeError, out short cchActual) {
            // ODBC (MSDN) documents it expects a buffer large enough to hold 4(+L'\0') unicode characters
            StringBuilder sb = new StringBuilder(5);
            ODBC32.RetCode retcode = UnsafeNativeMethods.SQLGetDiagRecW(HandleType, this, record, sb, out nativeError, message, checked((short)message.Capacity), out cchActual);
            ODBC.TraceODBC(3, "SQLGetDiagRecW", retcode);

            if ((retcode == ODBC32.RetCode.SUCCESS) || (retcode == ODBC32.RetCode.SUCCESS_WITH_INFO)) {
                sqlState = sb.ToString();
            }
            else {
                sqlState = ADP.StrEmpty;
            }
            return retcode;
        }
    }

    sealed internal class OdbcDescriptorHandle : OdbcHandle {

        internal OdbcDescriptorHandle(OdbcStatementHandle statementHandle, ODBC32.SQL_ATTR attribute) : base(statementHandle, attribute) {
        }

        internal ODBC32.RetCode GetDescriptionField(int i, ODBC32.SQL_DESC attribute, CNativeBuffer buffer, out int numericAttribute) {
            ODBC32.RetCode retcode = UnsafeNativeMethods.SQLGetDescFieldW(this, checked((short)i), attribute, buffer, buffer.ShortLength, out numericAttribute);
            ODBC.TraceODBC(3, "SQLGetDescFieldW", retcode);
            return retcode;
        }

        internal ODBC32.RetCode SetDescriptionField1(short ordinal, ODBC32.SQL_DESC type, IntPtr value) {
            ODBC32.RetCode retcode = UnsafeNativeMethods.SQLSetDescFieldW(this, ordinal, type, value, 0);
            ODBC.TraceODBC(3, "SQLSetDescFieldW", retcode);
            return retcode;
        }

        internal ODBC32.RetCode SetDescriptionField2(short ordinal, ODBC32.SQL_DESC type, HandleRef value) {
            ODBC32.RetCode retcode = UnsafeNativeMethods.SQLSetDescFieldW(this, ordinal, type, value, 0);
            ODBC.TraceODBC(3, "SQLSetDescFieldW", retcode);
            return retcode;
        }
    }

}

